var __createBinding =
    (this && this.__createBinding) ||
    (Object.create
        ? (o, m, k, k2) => {
              if (k2 === undefined) k2 = k;
              var desc = Object.getOwnPropertyDescriptor(m, k);
              if (
                  !desc ||
                  ("get" in desc
                      ? !m.__esModule
                      : desc.writable || desc.configurable)
              ) {
                  desc = { enumerable: true, get: () => m[k] };
              }
              Object.defineProperty(o, k2, desc);
          }
        : (o, m, k, k2) => {
              if (k2 === undefined) k2 = k;
              o[k2] = m[k];
          });
var __setModuleDefault =
    (this && this.__setModuleDefault) ||
    (Object.create
        ? (o, v) => {
              Object.defineProperty(o, "default", {
                  enumerable: true,
                  value: v,
              });
          }
        : (o, v) => {
              o.default = v;
          });
var __importStar =
    (this && this.__importStar) ||
    ((mod) => {
        if (mod?.__esModule) return mod;
        var result = {};
        if (mod != null)
            for (var k in mod)
                if (k !== "default" && Object.hasOwn(mod, k))
                    __createBinding(result, mod, k);
        __setModuleDefault(result, mod);
        return result;
    });
var __importDefault =
    (this && this.__importDefault) ||
    ((mod) => (mod?.__esModule ? mod : { default: mod }));
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProxyFunctionVariable =
    exports.ProxyFunction =
    exports.isProxyFunctionExpression =
        void 0;
const t = __importStar(require("@babel/types"));
const misc_1 = require("../../helpers/misc");
const traverse_1 = __importDefault(require("@babel/traverse"));
/**
 * Returns whether a proxy function expression.
 * @param node The node.
 * @returns Whether.
 */
const isProxyFunctionExpression = (node) => {
    return (
        t.isFunction(node) &&
        node.params.every((p) => t.isIdentifier(p)) &&
        ((t.isBlockStatement(node.body) &&
            node.body.body.length === 1 &&
            t.isReturnStatement(node.body.body[0]) &&
            (node.body.body[0].argument === undefined ||
                (t.isExpression(node.body.body[0].argument) &&
                    isProxyValue(node.body.body[0].argument)))) ||
            (t.isArrowFunctionExpression(node) &&
                t.isExpression(node.body) &&
                isProxyValue(node.body)))
    );
};
exports.isProxyFunctionExpression = isProxyFunctionExpression;
/**
 * Returns whether a node is a valid proxy function return value.
 * @param node The node.
 * @returns Whether.
 */
const isProxyValue = (node) => {
    if (
        t.isFunction(node) ||
        t.isBlockStatement(node) ||
        t.isSequenceExpression(node)
    ) {
        return false;
    }
    let isValid = true;
    (0, traverse_1.default)(node, {
        "SequenceExpression|BlockStatement|Function"(path) {
            isValid = false;
            path.stop();
        },
        noScope: true,
    });
    return isValid;
};
class ProxyFunction {
    /**
     * Creates a new proxy function.
     * @param expression The proxy function expression.
     */
    constructor(expression) {
        this.expression = expression;
    }
    /**
     * Returns the replacement for a call of the proxy function.
     * @param args The arguments of the call.
     * @returns The replacement expression.
     */
    getReplacement(args) {
        const expression = t.isExpression(this.expression.body)
            ? (0, misc_1.copyExpression)(this.expression.body)
            : this.expression.body.body[0].argument
              ? (0, misc_1.copyExpression)(
                    this.expression.body.body[0].argument,
                )
              : t.identifier("undefined");
        this.replaceParameters(expression, args);
        return expression;
    }
    /**
     * Replaces usages of the proxy function's parameters with the concrete arguments for a given call.
     * @param expression The expression.
     * @param args The arguments of the call.
     */
    replaceParameters(expression, args) {
        const paramMap = new Map(
            this.expression.params.map((param, index) => [
                param.name,
                args[index] || t.identifier("undefined"),
            ]),
        );
        const pathsToReplace = [];
        (0, traverse_1.default)(expression, {
            enter(path) {
                if (t.isIdentifier(path.node) && paramMap.has(path.node.name)) {
                    const replacement = paramMap.get(path.node.name);
                    pathsToReplace.push([path, replacement]);
                }
            },
            noScope: true,
        });
        for (const [path, replacement] of pathsToReplace) {
            path.replaceWith(replacement);
        }
    }
}
exports.ProxyFunction = ProxyFunction;
class ProxyFunctionVariable extends ProxyFunction {
    /**
     * Creates a new proxy function variable.
     * @param variable The variable.
     */
    constructor(variable) {
        super(variable.expression);
        this.variable = variable;
    }
    /**
     * Returns the calls to the proxy function.
     * @returns The calls to the proxy function.
     */
    getCalls() {
        return this.variable.binding.referencePaths;
    }
    /**
     * Attempts to replace a call of the proxy function.
     * @param path The path of the call.
     * @returns Whether it was replaced.
     */
    replaceCall(path) {
        if (path.parentPath?.isCallExpression() && path.key === "callee") {
            const expression = this.getReplacement(
                path.parentPath.node.arguments,
            );
            path.parentPath.replaceWith(expression);
            return true;
        } else {
            return false;
        }
    }
}
exports.ProxyFunctionVariable = ProxyFunctionVariable;
